#include "lib/drivers/pvmdriver_rawwriter.h"
#include "lib/drivers/pvmdeviceinterface.h"
#include "lib/pvmerrors.h"
#include "lib/helpers/pvmoutstreamhelpers.h"
#include "lib/pvmstrings.h"
#include "lib/pvmdriver.h"

#include "lib/memory/pvmmemory.h"

#define PVMDRAWWRITER_WAIT_FOR_SUSPEND		1000
#define PVMDRAWWRITER_DEFAULT_BUFFER_LENGTH	512
#define PVMDRAWWRITER_DEVICE_TYPE	EPVM_DTYPE_OUTPUT|EPVM_DTYPE_STREAM_WRITER

class PVMDeviceRawWriter : public IPVMDeviceOutSimple, public PVMAllocableObject
{
public:
	PVMDeviceRawWriter(PVMDriverRawWriter* driver);
	~PVMDeviceRawWriter();

	virtual pvm_uint32 GetDeviceType();
	virtual void DeviceClose();
	virtual pvm_bool DeviceOpened();

	virtual pvm_bool SetOption(const pvm_char* optionName, void* param, pvm_uint32 paramSize);
	virtual pvm_bool GetOption(const pvm_char* optionName, void* param, pvm_uint32* paramSize);

	virtual void Suspend();
	virtual pvm_bool IsSuspended();
	virtual void Resume();

	virtual void DeviceNotify();

	virtual void SetBufferLengthMs(pvm_uint32 bufferSize);
	virtual pvm_uint32 GetBufferRealLengthMs();

	virtual pvm_int32 GetCPUUsage();

	virtual pvm_uint32 GetChanelsNum();
	virtual pvm_uint32 GetBitsPerSample();
	virtual pvm_uint32 GetSamplesPerSecond();

	//static PVMDeviceRawWriter* DeviceOpen(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended);

	pvm_int32 GetHandle() {return handle;};
	void SetHandle(pvm_int32 val) {handle = val;};
	PVMDeviceRawWriter* NextDevice() {return nextDevice;};
	PVMDeviceRawWriter* PrevDevice() {return prevDevice;};
	void SetNextDevice(PVMDeviceRawWriter* device) {nextDevice = device;};
	void SetPrevDevice(PVMDeviceRawWriter* device) {prevDevice = device;};

	pvm_bool Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended);

	IPVMDriverBase* GetDriver();
protected:
	void DeInit();

	pvm_bool isDeviceInitialized;

	PVMCriticalSection pvmdGlobalVarGuard;

	pvm_bool runThread;
	pvm_bool isThreaRunning;

	pvm_bool pauseRendering;
	pvm_bool isRenderingPaused;

	pvm_uint32			currentSampleRate;
	pvm_uint32			currentbitsPerSample;
	pvm_uint32			currentbytesPerSample;
	pvm_uint32			currentChanelsNum;
	IPvmOutStream*		outStream;

	//rendering sources
	PVMCriticalSection pvmdRenderGuard;
	pvm_uint32 currentBufferLength;
	pvm_uint32 newBufferLength;

	int renderBufferSize;
	int renderBufferSizeInSamples;
	pvm_int8* renderBuffer;

	PVMThreadHandle hPlayThread;
	PVMEventHandle hPlayEvent;
	static PVMThreadRetVal PVMCALL PlaybackThreadProc(void* lpParameter);

	void PlaybackLoop();
	bool ResizeRenderBuffer(int newLengthMs);

	pvm_int32 handle;
	PVMDeviceRawWriter* nextDevice;
	PVMDeviceRawWriter* prevDevice;

	IPVMDeviceOutSimpleWaveSource *waveSource;

	PVMDriverRawWriter* driver;
};

pvm_uint32 PVMDeviceRawWriter::GetChanelsNum()
{
	return currentChanelsNum;
}

pvm_uint32 PVMDeviceRawWriter::GetBitsPerSample()
{
	return currentbitsPerSample;
}

pvm_uint32 PVMDeviceRawWriter::GetSamplesPerSecond()
{
	return currentSampleRate;
}

pvm_int32 PVMDeviceRawWriter::GetCPUUsage()
{
	return -1;
}

PVMDeviceRawWriter::PVMDeviceRawWriter(PVMDriverRawWriter* driver)
{
	isDeviceInitialized = false;

	//threading vars
	PVMInitThreadVar(&hPlayThread);

	runThread = false;
	isThreaRunning = false;

	pauseRendering = false;
	isRenderingPaused = false;

	currentSampleRate = 0;
	currentbitsPerSample = 0;
	currentbytesPerSample = 0;
	currentChanelsNum = 0;

	currentBufferLength = 0;
	newBufferLength = PVMDRAWWRITER_DEFAULT_BUFFER_LENGTH;

	renderBufferSize = 0;
	renderBufferSizeInSamples = 0;

	renderBuffer = NULL;

	outStream = NULL;

	handle = 0;
	nextDevice = 0;
	prevDevice = 0;

	waveSource = NULL;

	this->driver = driver;
}

PVMDeviceRawWriter::~PVMDeviceRawWriter()
{
	DeInit();
}

pvm_bool PVMDeviceRawWriter::Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 /*deviceNum*/, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	PVMInitCritical(&pvmdGlobalVarGuard);
	PVMInitCritical(&pvmdRenderGuard);

	PVMEnterCritical(&pvmdGlobalVarGuard);
	
	isDeviceInitialized = false;
	runThread = true;
	isThreaRunning = false;

	pauseRendering = deviceSuspended;
	isRenderingPaused = false;	

	PVMInitEvent(&hPlayEvent);

	PVMInitThreadVar(&hPlayThread);
	if(!PVMCreateThread(&hPlayThread, PVMDeviceRawWriter::PlaybackThreadProc, (void*)this))
	{
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		PVMDeinitCritical(&pvmdGlobalVarGuard);
		PVMDeinitCritical(&pvmdRenderGuard);
		PVMDeinitEvent(&hPlayEvent);
		return false;
	}

	PVMLeaveCritical(&pvmdGlobalVarGuard);

	if(!PVMIsThreadValid(hPlayThread))
	{
		PVMDeinitCritical(&pvmdGlobalVarGuard);
		PVMDeinitCritical(&pvmdRenderGuard);
		PVMDeinitEvent(&hPlayEvent);
		return false;
	}

	PVMEnterCritical(&pvmdGlobalVarGuard);

	currentSampleRate = samplesPerSec;
	currentbitsPerSample = bitsPerSample;
	currentbytesPerSample = bitsPerSample/8;
	currentChanelsNum = chanelsNum;
	if(infoSize==sizeof(IPVMWriteCallbacksSource))
	{
		outStream = new PVMOutCallbacksStream(((IPVMWriteCallbacksSource*)additionalInfo)->callBacks, ((IPVMWriteCallbacksSource*)additionalInfo)->src);
	}
	else
	{
		outStream = (IPvmOutStream*)additionalInfo;
	}

	isDeviceInitialized = true;
	this->waveSource = waveSource;

	PVMLeaveCritical(&pvmdGlobalVarGuard);
	DeviceNotify();

	return true;
}

void PVMDeviceRawWriter::DeInit()
{
	if(isDeviceInitialized)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		runThread  = false;
		PVMLeaveCritical(&pvmdGlobalVarGuard);


		if(isRenderingPaused)
			Resume();

		while(isThreaRunning);
	}

	if(DeviceOpened())
	{
		if(outStream)
			outStream->Close();

		outStream = NULL;
	}

	if(isDeviceInitialized)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		isDeviceInitialized = false;

		PVMLeaveCritical(&pvmdGlobalVarGuard);

		PVMDeinitEvent(&hPlayEvent);

		PVMDeinitCritical(&pvmdRenderGuard);
		PVMDeinitCritical(&pvmdGlobalVarGuard);
	}

	waveSource = NULL;

	if(renderBuffer!=NULL)
		PVMMemoryFree(renderBuffer);
}

pvm_uint32 PVMDeviceRawWriter::GetDeviceType()
{
	return PVMDRAWWRITER_DEVICE_TYPE;
}

void PVMDeviceRawWriter::DeviceClose()
{
	DeInit();
	delete( this);
}

pvm_bool PVMDeviceRawWriter::DeviceOpened()
{
	return outStream!=NULL;
}

pvm_bool PVMDeviceRawWriter::SetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32 /*paramSize*/)
{
	return false;
}

pvm_bool PVMDeviceRawWriter::GetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32* /*paramSize*/)
{
	return false;
}

void PVMDeviceRawWriter::Suspend()
{
	if(pauseRendering)
		return;
	
	PVMEnterCritical(&pvmdGlobalVarGuard);
	pauseRendering = true;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	pvm_uint32 time1 = PVMGetMilliSecondsCount();
	pvm_uint32 time2;
	if(PVMIsThreadValid(hPlayThread))
	{
		while(!isRenderingPaused)
		{
			time2 = PVMGetMilliSecondsCount();
			if(time2-time1>PVMDRAWWRITER_WAIT_FOR_SUSPEND)
				break;
			//check for long overflow - will no hang but will wait more than WAIT_FOR_SUSPEND
			if(time1>time2)
				time1 = time2;

			PVMSleep(1);
		}
	}
}

pvm_bool PVMDeviceRawWriter::IsSuspended()
{
	return isRenderingPaused&&pauseRendering;
}

void PVMDeviceRawWriter::Resume()
{
	PVMEnterCritical(&pvmdGlobalVarGuard);
	pauseRendering = false;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	if(isRenderingPaused)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		isRenderingPaused = false;
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		PVMSignalEvent(&hPlayEvent);
	}
}

void PVMDeviceRawWriter::DeviceNotify()
{
	if(!pauseRendering&&isRenderingPaused)
	{
		//PVMEnterCritical(&globalVarGuard);
		isRenderingPaused = false;
		//PVMLeaveCritical(&globalVarGuard);
		if(PVMIsThreadValid(hPlayThread))
			PVMSignalEvent(&hPlayEvent);
	}
}

void PVMDeviceRawWriter::SetBufferLengthMs(pvm_uint32 bufferSize)
{
	newBufferLength = bufferSize;
	DeviceNotify();
}

pvm_uint32 PVMDeviceRawWriter::GetBufferRealLengthMs()
{
	return currentBufferLength;
}

PVMThreadRetVal PVMCALL PVMDeviceRawWriter::PlaybackThreadProc(void* lpParameter)
{
	((PVMDeviceRawWriter*)lpParameter)->PlaybackLoop();
	return 0;
}

void PVMDeviceRawWriter::PlaybackLoop()
{
	isThreaRunning = true;
	bool needSuspndThread = false;
	//bool isWaveOutError = false;

	while(runThread)
	{
		needSuspndThread = false;

		if(waveSource==NULL || waveSource->IsInitialized()==false)
			needSuspndThread = true;
		else if((pauseRendering&&!isRenderingPaused)||(!outStream)||!waveSource->HasMoreData())
			needSuspndThread = true;

		if(needSuspndThread)
		{
			PVMEnterCritical(&pvmdGlobalVarGuard);
			isRenderingPaused = true;
			needSuspndThread = false;
			PVMLeaveCritical(&pvmdGlobalVarGuard);
			PVMWaitForEvent(&hPlayEvent);
		}

		if((currentBufferLength!=newBufferLength || renderBuffer==NULL) && (waveSource!=NULL) && (waveSource->IsInitialized()!=false))
		{
			if(!ResizeRenderBuffer(newBufferLength))
			{
				//handle error
			}
		}


		if(waveSource!=NULL && waveSource->HasMoreData() && renderBuffer!=NULL)
		{
			pvm_uint32 samplesFilled = 0;
			PVMEnterCritical(&pvmdRenderGuard);
			samplesFilled = waveSource->FillBuffer(renderBuffer, renderBufferSizeInSamples);
			PVMLeaveCritical(&pvmdRenderGuard);

			if(outStream&&samplesFilled>0)
			{
				outStream->Write(renderBuffer, samplesFilled*currentbytesPerSample);
			}
			//end of rendering
		}
		else
		{
			//Cache?
			//PVMEnterCritical(&pvmdRenderGuard);
			//PVMLeaveCritical(&pvmdRenderGuard);
			PVMSleep(1);
		}
		PVMSleep(1);
	}

	PVMEnterCritical(&pvmdGlobalVarGuard);
	isThreaRunning = false;
	pauseRendering = false;
	isRenderingPaused = false;
	PVMLeaveCritical(&pvmdGlobalVarGuard);
}


bool PVMDeviceRawWriter::ResizeRenderBuffer(int newLengthMs)
{
	PVMEnterCritical(&pvmdGlobalVarGuard);
	int sampleRate = currentSampleRate;
	int sampleSize = currentbytesPerSample;
	int chanelsNum = currentChanelsNum;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	int renderStepSize = sampleSize*chanelsNum;
	int newRenderBufferSizeInBites = sampleRate*renderStepSize*newLengthMs/1000;
	//buffer size must be multiple of chanelNum*sampleSize
	newRenderBufferSizeInBites = (newRenderBufferSizeInBites/renderStepSize)*renderStepSize;

	PVMEnterCritical(&pvmdRenderGuard);
	renderBufferSize = newRenderBufferSizeInBites;
	renderBufferSizeInSamples = renderBufferSize/sampleSize;
	currentBufferLength = newLengthMs;
	PVMMemoryFree(renderBuffer);
	renderBuffer = (pvm_int8*)PVMMemoryAlloc(renderBufferSize);
	PVMLeaveCritical(&pvmdRenderGuard);

	if(waveSource!=NULL)
		waveSource->ResizeRenderBuffer(renderBufferSize);

	return true;
}

IPVMDriverBase* PVMDeviceRawWriter::GetDriver()
{
	return driver;
}

////////////////////////////////////////////////////////////////////////////////
//PVMDriverRawWriter															  //
////////////////////////////////////////////////////////////////////////////////

PVMDriverRawWriter::PVMDriverRawWriter()
{
}

PVMDriverRawWriter::~PVMDriverRawWriter()
{
}

pvm_bool PVMDriverRawWriter::Init()
{

	return true;
}

pvm_bool PVMDriverRawWriter::IsInitialized()
{
	return true;
}

void PVMDriverRawWriter::DriverClose()
{
	delete( this);
}

pvm_uint32 PVMDriverRawWriter::GetDriverType()
{
	return PVMDRAWWRITER_DEVICE_TYPE;
}

pvm_bool PVMDriverRawWriter::GetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32* /*optionSize*/)
{
	return false;
}

pvm_bool PVMDriverRawWriter::SetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32 /*optionSize*/)
{
	return false;
}


pvm_uint32 PVMDriverRawWriter::DevicesNum()
{
	return 0;
}

pvm_int32 PVMDriverRawWriter::DeviceName(pvm_int32 /*nDevice*/, pvm_char* buffer, pvm_uint32 maxLength)
{
	if(buffer==0||maxLength==0)
		return 11;
	if(maxLength<11)
		return 0;
	pvm_strcpy(buffer, _PVM_STR("raw writer"));

	return pvm_strlen(buffer);
}

pvm_bool PVMDriverRawWriter::DeviceEnumModes(pvm_int32 /*nDeivce*/, TFormatEnumCallbackProc enumProc, void *param)
{
	if(!enumProc)
		return false;

	enumProc(1, 8, 11025, param);
	enumProc(1, 8, 22050, param);
	enumProc(1, 8, 44100, param);
	enumProc(1, 8, 48000, param);
	enumProc(1, 8, 96000, param);

	enumProc(1, 16, 11025, param);
	enumProc(1, 16, 22050, param);
	enumProc(1, 16, 44100, param);
	enumProc(1, 16, 48000, param);
	enumProc(1, 16, 96000, param);

	enumProc(2, 8, 11025, param);
	enumProc(2, 8, 22050, param);
	enumProc(2, 8, 44100, param);
	enumProc(2, 8, 48000, param);
	enumProc(2, 8, 96000, param);

	enumProc(2, 16, 11025, param);
	enumProc(2, 16, 22050, param);
	enumProc(2, 16, 44100, param);
	enumProc(2, 16, 48000, param);
	enumProc(2, 16, 96000, param);

	return true;
}

IPVMDeviceOutSimple* PVMDriverRawWriter::DeviceOpen(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	if(!IsInitialized())
		return NULL;

	PVMDeviceRawWriter* device = new PVMDeviceRawWriter(this);
	if(!device)
		return NULL;
	if(!device->Init(samplesPerSec, bitsPerSample, chanelsNum, deviceNum, additionalInfo, infoSize, waveSource, deviceSuspended))
	{
		device->DeviceClose();
		return NULL;
	}

	return device;
}

void PVMDriverRawWriter::CloseDevice(IPVMDeviceBase* device)
{
	if(device!=NULL)
		device->DeviceClose();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

IPVMDriverOut* PVMDriverCreateRawWriter()
{
	PVMDriverRawWriter* outDrv = new PVMDriverRawWriter();
	if(!outDrv)
		return NULL;
	PVMDriverSimple* simpledrv = new PVMDriverSimple(outDrv);
	if(!simpledrv)
	{
		outDrv->DriverClose();
	}

	return simpledrv;
}

static pvm_int32 DriverNameShort(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("raw writer"), buffer, maxLength);
}

static pvm_int32 DriverNameFull(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("RAW data writer"), buffer, maxLength);
}

static pvm_uint32 DriverTypeAndFlags()
{
	return PVMDRAWWRITER_DEVICE_TYPE;
}

static IPVMDriverBase* DriverLoad()
{
	return PVMDriverCreateRawWriter();
}

IPVMDriverDesc	rawWriterDesc;

IPVMDriverDesc* PVMGetDriverDescRawWriter()
{
	rawWriterDesc.DriverNameShort = DriverNameShort;
	rawWriterDesc.DriverNameFull = DriverNameFull;
	rawWriterDesc.DriverTypeAndFlags = DriverTypeAndFlags;
	rawWriterDesc.DriverLoad = DriverLoad;
	return &rawWriterDesc;
}

#undef	PVMDRAWWRITER_WAIT_FOR_SUSPEND
#undef	PVMDRAWWRITER_DEFAULT_BUFFER_LENGTH
#undef	PVMDRAWWRITER_DEVICE_TYPE
